﻿using System;
using System.Linq.Expressions;
using System.Reflection;
using System.Windows.Input;

namespace RG.Common.WPF.Commands
{
    /// <summary>
    /// Биндинг для комманд, который делегирует CanExecute и Execute на методы DataContext 
    /// помеченные атрибутами <see cref="NamedCanExecuteAttribute"/> и <see cref="NamedExecuteAttribute"/>
    /// </summary>
    public class NamedCommandBinding : BaseCommandBinding 
    {
        private Action<object> onExecute;
        private Func<object, bool> onCanExecute;

        /// <summary>
        /// Имя комманды, по которому ищется метод для выполнения (должен совпадаться с именем в атрибуте) 
        /// </summary>
        public string CommandName { get; set; }
        /// <summary>
        /// если тру то в методы повешенные на onCanExecute и onExecute передается не object obj, а EventArgs.Parameter (в методах InternalCanExecute и InternalExecute)
        /// </summary>
        public bool UseEventArgParameter { get; set; }
        private static Func<object, bool> CreateCanExecute(object obj, MethodInfo method)
        {
            var param = Expression.Parameter(typeof(object), "obj");
            Expression body = Expression.Call(Expression.Constant(obj), method, param);
            var call = Expression.Lambda<Func<object, bool>>(body, param);
            return call.Compile();
        }

        private static Action<object> CreateExecute(object obj, MethodInfo method)
        {
            var param = Expression.Parameter(typeof(object), "obj");
            Expression body = Expression.Call(Expression.Constant(obj), method, param);
            var call = Expression.Lambda<Action<object>>(body, param);
            return call.Compile();
        }


        protected override void OnSetLastContext(object lastContext)
        {
            if (lastContext == null)
            {
                onExecute = null;
                onCanExecute = null;
                return;
            }
            
            var tp = lastContext.GetType();
            foreach (var mi in tp.GetMethods())
            {
                foreach (var attr in Attribute.GetCustomAttributes(mi))
                {
                    if (attr.GetType() == typeof(NamedCanExecuteAttribute) &&
                        ((NamedCanExecuteAttribute)attr).Name == CommandName)
                    {
                        onCanExecute = CreateCanExecute(lastContext, mi);
                    }
                    if (attr.GetType() == typeof(NamedExecuteAttribute) &&
                        ((NamedExecuteAttribute)attr).Name == CommandName)
                    {
                        onExecute = CreateExecute(lastContext, mi);
                    }
                }
            }
        }

        protected override void InternalCanExecute(object obj, CanExecuteRoutedEventArgs args)
        {
            if (onCanExecute == null)
            {
                args.Handled = args.CanExecute = onExecute != null;
                return;
            }
            args.CanExecute = onCanExecute(UseEventArgParameter ? args.Parameter : obj);
            args.Handled = true;
        }

        protected override void InternalExecute(object obj, ExecutedRoutedEventArgs args)
        {
            if (onExecute == null) return;
            //note - вот тут бы сделать чтобы можно было испльзовать сигнатуру не только с параметром типа Action<object>
            onExecute(UseEventArgParameter?args.Parameter:obj);
            args.Handled = true;
            return;
        }
    }
}
